# -*- coding: utf-8 -*-
# @Author: Jerry
# @Date:   2022-03-01 10:49:24
# @Last Modified by:   Jerry
# @Last Modified time: 2022-03-01 17:38:39


# **********
# OpenCV中的图像处理 » 4_10_1_直方图-1：查找，绘制，分析
# http://www.woshicver.com

# **********
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import os
rootpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
datapath = os.path.join(rootpath,'data')
imgpath = lambda name: os.path.join(datapath,name)


# **********
'''目标
** 学会 **
-- 使用OpenCV和Numpy函数查找直方图
-- 使用OpenCV和Matplotlib函数绘制直方图
-- 你将看到以下函数：cv.calcHist()，np.histogram()等。

** 理论 **
直方图是什么？您可以将直方图视为图形或绘图，从而可以总体了解图像的强度分布。
它是在X轴上具有像素值（不总是从0到255的范围），在Y轴上具有图像中相应像素数的图。

直方图是理解图像的另一种方式。
通过查看图像的直方图，您可以直观地了解该图像的对比度，亮度，强度分布等。

# 简单理解，一幅图是由一个个像素来组成的，而每个像素又是由不同的值表达的
# 比如一个灰度图，一个像素其值是介于0-255之间的，
# 而一幅图中，将所有的像素值做一个统计，有多个像素的值是0，多少个像素的值是1，...多少个像素的值是255
# 用统计结果做出直方图，可反应一幅图的像素分布情况

直方图是针对灰度图像而非彩色图像绘制的
直方图的左侧区域显示图像中较暗像素的数量，而右侧区域则显示明亮像素的数量。

** 寻找直方图 **
OpenCV和Numpy都为寻找直方图内置了功能。
一些与直方图有关的术语：
-- BINS：完整的直方图显示每个像素值的像素数，即从0到255。即，您需要256个值来显示完整的直方图。
但是考虑一下，如果您不需要分别找到所有像素值的像素数，而是找到像素值间隔中的像素数怎么办？

例如，您需要找到介于0到15之间的像素数，然后找到16到31之间，…，240到255之间的像素数。
因此，您要做的就是将整个直方图分成16个子部分，每个子部分的值就是其中所有像素数的总和。
每个子部分都称为“ BIN”。

因此BIN的个数 = 256/强度值分割区间宽度，如0-15时，强度值分割区间宽度为16，所以BIN=256/16=16
BINS由OpenCV文档中的 histSize 术语表示。

-- DIMS：这是我们为其收集数据的参数的数量。在这种情况下，我们仅收集关于强度值的一件事的数据。
-- RANGE：这是您要测量的强度值的范围。通常，它是[0,256]，即所有强度值。
'''


# **********
'''1. OpenCV中的直方图计算
opencv使用 cv.calcHist()函数查找直方图。
cv.calcHist（images，channels，mask，histSize，ranges [，hist [，accumulate]]）
1.mages：它是uint8或float32类型的源图像。它应该放在方括号中，即“ [img]”。
2.channels：也以方括号给出。它是我们计算直方图的通道的索引。
  例如，如果输入为灰度图像，则其值为[0]。
  对于彩色图像，您可以传递[0]，[1]或[2]分别计算蓝色，绿色或红色通道的直方图。
3.mask：图像掩码。
  为了找到完整图像的直方图，将其指定为“无”。
  但是，如果要查找图像特定区域的直方图，则必须为此创建一个掩码图像并将其作为掩码。（我将在后面显示一个示例。）
4.histSize：这表示我们的BIN计数。需要放在方括号中。对于全尺寸，我们通过[256]。
5.ranges：这是我们的RANGE。通常为[0,256]。'''

blueimg = cv.imread(imgpath('home.jpg'),0)
print blueimg.shape
hist = cv.calcHist([blueimg],[0],None,[256],[0,256])
# hist是256x1的数组，每个值对应于该图像中具有相应像素值的像素数。
# print hist

# **********
'''2. numpy的直方图计算
Numpy提供了一个函数 np.histogram()。因此，除了 calcHist()函数外，还可以尝试下面的代码：'''
hist,bins = np.histogram(blueimg.ravel(),256,[0,256])
# hist与我们之前计算的相同。
# 但是bin将具有257个元素，因为Numpy计算出bin的范围为0-0.99、1-1.99、2-2.99等。因此最终范围为255-255.99。
# 为了表示这一点，他们还在最后添加了256。但是我们不需要256。最多255就足够了。
# print hist

# Numpy还有另一个函数 np.bincount()，它比np.histogram()快10倍左右。
# 因此，对于一维直方图，您可以更好地尝试一下。
# 不要忘记在np.bincount中设置minlength = 256。
hist = np.bincount(blueimg.ravel(),minlength = 256)
# print hist
# OpenCV函数比np.histogram()快大约40倍。因此，尽可能使用OpenCV函数。


# **********
'''3.绘制直方图
有两种方法，
1. 简短的方法：使用Matplotlib绘图功能
2. 稍长的方法：使用OpenCV绘图功能'''


# **********
'''3.1. 使用Matplotlib
Matplotlib带有直方图绘图功能：matplotlib.pyplot.hist()它直接找到直方图并将其绘制。
您无需使用 cv.calcHist() 或 np.histogram() 函数来查找直方图。'''
plt.subplot(121);plt.hist(blueimg.ravel(),256,[0,256]);plt.title('blue histogram');
# plt.xticks([]),plt.yticks([])

# **********
# 或者，您可以使用matplotlib的法线图，这对于BGR图是很好的。
img = cv.imread(imgpath('home.jpg'))
color = ('b','g','r')
plt.subplot(122)
for i,col in enumerate(color):
    histr = cv.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color=col)
    plt.xlim([0,256])
plt.title('b,g,r histogram with line')
# plt.show()

# **********
'''3.2使用 OpenCV
在这里您可以调整直方图的值及其bin值，使其看起来像x，y坐标，
以便您可以使用 cv.line() 或 cv.polyline() 函数绘制它以生成与上述相同的图像。
检查示例/python/hist.py'''
# calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) -> hist
imggray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
hist_item = cv.calcHist([imggray],[0],None,[256],[0,256])
cv.normalize(hist_item,hist_item,0,255,cv.NORM_MINMAX)
# 在画直方图之前，先使用 normalize 归一化直方图，
# 这样直方图bin中的值就被缩放到指定范围（这个步骤只是为了让直方图的值可以显示在目标窗口中）
# cv.normalize(src, dst[, alpha[, beta[, norm_type[, dtype[, mask]]]]]) -> dst
hist = np.int32(np.around(hist_item))
# print 'hist = ',hist
#
#numpy.around(a, decimals=0, out=None)
#  a为输入列表或矩阵
#  decimals为n对输入近似后保留小数点后n位，默认为0，若值为-n，则对小数点左边第n位近似；
#  out为可选参数，一般不用，用于保存近似返回结果。
#np.around 返回四舍五入后的值，可指定精度
#  a 输入数组
#  decimals 要舍入的小数位数。 默认值为0。 如果为负，整数将四舍五入到小数点左侧的位置
#np.int32
# 将一个list中的数据全部转化为numpy.int32类型的

# print hist_item
imghist = np.zeros((300,256,3)) # width,height,channels
for x,y in enumerate(hist): # hist的结构为[[2],[3],...[0]]
    # x为hist的index[0,255]，y=[2],[3]......
    cv.line(imghist,(x,0),(x,y[0]),(0,255,0))
imghist = np.flipud(imghist)
# numpy的资讯：https://www.runoob.com/numpy/numpy-tutorial.html
# np.flipud()用于翻转列表，将矩阵进行上下翻转
# 之所以要翻转，与系统定义的原点有关。
# cv.imshow('hist',imghist)
# cv.waitKey(0)

# **********
'''掩码的应用
使用了cv.calcHist()来查找整个图像的直方图, 如果你想找到图像某些区域的直方图
只需创建一个掩码图像，在你要找到直方图为白色，否则黑色。然后把这个作为掩码传递。'''

img = cv.imread(imgpath('home.jpg'),0)
# create a mask
mask = np.zeros(img.shape[:2],np.uint8)
mask[100:300,100:400] = 255
masked_img = cv.bitwise_and(img,img,mask=mask) #and:一个为0结果就是0
# 计算掩码区域和非掩码区域的直方图
# 检查作为掩码的第三个参数
hist_full = cv.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv.calcHist([img],[0],mask,[256],[0,256])
plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask,'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])
plt.show()



